package com.adamfass.jmetest;

import java.util.logging.Logger;

import com.jme.image.Texture;
import com.jme.input.InputSystem;
import com.jme.input.joystick.JoystickInput;
import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Cylinder;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.util.SimplePhysicsGame;

public class Test2 extends SimplePhysicsGame {
  private static final Logger LOG = Logger.getLogger(Test2.class.getName());
  
  JoystickInput input;
  DynamicPhysicsNode saucerPhysicsNode;
  DynamicPhysicsNode ball;
  
  DynamicPhysicsNode movingStaticNode;
  
  Node beam = null;
  
  private void addConstraints(DynamicPhysicsNode node) {
    final DynamicPhysicsNode dummyNode = 
      getPhysicsSpace().createDynamicNode();
    rootNode.attachChild(dummyNode);
    
    final Joint xAxisJoint = getPhysicsSpace().createJoint();
    xAxisJoint.attach(dummyNode);
    xAxisJoint.createTranslationalAxis().setDirection(Vector3f.UNIT_X);
    
    final Joint yAxisJoint = getPhysicsSpace().createJoint();
    yAxisJoint.attach(dummyNode, node);
    yAxisJoint.createTranslationalAxis().setDirection(Vector3f.UNIT_Y);
  }

  protected void simpleInitGame() {
    final Sphere planet = new Sphere("earth", 40, 20, 30);
    planet.setLocalTranslation(-15, -20, -1000);
    planet.setLocalRotation(
            new Quaternion().fromAngleAxis(-0.3f, Vector3f.UNIT_Z)
            .multLocal(new Quaternion().fromAngleAxis(-FastMath.PI / 2, Vector3f.UNIT_X)));
            
    final TextureState earthTextureState = 
      display.getRenderer().createTextureState();
    earthTextureState.setEnabled(true);
    final Texture earthTexture = TextureManager.loadTexture(
            Test1.class.getClassLoader().getResource(
                      "com/adamfass/jmetest/jupiter_surface.jpg"), 
            Texture.MM_LINEAR, Texture.FM_LINEAR);
    earthTextureState.setTexture(earthTexture);
    planet.setRenderState(earthTextureState);
    rootNode.attachChild(planet);
    
    getPhysicsSpace().setDirectionalGravity(Vector3f.ZERO);
    
    saucerPhysicsNode = getPhysicsSpace().createDynamicNode();
    rootNode.attachChild(saucerPhysicsNode);
    saucerPhysicsNode.createSphere("saucer");
    addConstraints(saucerPhysicsNode);
    
    Sphere saucerDisc = new Sphere("saucer disc", 10, 20, 1);
    saucerDisc.setLocalScale(new Vector3f(0.9f, 0.25f, 0.9f));
    saucerPhysicsNode.attachChild(saucerDisc);
    final MaterialState discMaterialState = 
      display.getRenderer().createMaterialState();
    discMaterialState.setEnabled(true);
    discMaterialState.setDiffuse(ColorRGBA.green);
    discMaterialState.setAmbient(ColorRGBA.green);
    saucerDisc.setRenderState(discMaterialState);
    
    Sphere saucerBubble = new Sphere("saucer bubble", 10, 10, 0.25f);
    saucerBubble.setLocalTranslation(0, 0.25f, 0);
    saucerPhysicsNode.attachChild(saucerBubble);
    saucerBubble.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
    final MaterialState bubbleMaterialState = 
      display.getRenderer().createMaterialState();
    bubbleMaterialState.setEnabled(true);
    bubbleMaterialState.setAmbient(new ColorRGBA(0.5f, 0.5f, 1, 0.25f));
    bubbleMaterialState.setDiffuse(new ColorRGBA(0.5f, 0.5f, 1, 0.25f));
    bubbleMaterialState.setSpecular(new ColorRGBA(1, 1, 1, 0.25f));
    bubbleMaterialState.setShininess(10);
    bubbleMaterialState.setMaterialFace(MaterialState.MF_FRONT_AND_BACK);
    saucerBubble.setRenderState(bubbleMaterialState);
    
    final AlphaState bubbleAlphaState =
      display.getRenderer().createAlphaState();
    bubbleAlphaState.setEnabled(true);
    bubbleAlphaState.setBlendEnabled(true);
    bubbleAlphaState.setSrcFunction(AlphaState.SB_SRC_ALPHA);
    bubbleAlphaState.setDstFunction(AlphaState.SB_ONE_MINUS_SRC_ALPHA);
    bubbleAlphaState.setTestFunction(AlphaState.TF_GREATER);
    bubbleAlphaState.setNeedsRefresh(true);
    saucerBubble.setRenderState(bubbleAlphaState);
    
    ball = getPhysicsSpace().createDynamicNode();
    rootNode.attachChild(ball);
    ball.createSphere("ball");
    ball.setLocalTranslation(0, -5, 0);
    ball.setLocalScale(0.5f);
    addConstraints(ball);
    ball.attachChild(new Sphere("ball model", 20, 20, 1));
    
    ball.updateWorldVectors();
    saucerPhysicsNode.updateWorldVectors();
    setCameraLocationAndZoom(saucerPhysicsNode, ball);
    
    movingStaticNode = getPhysicsSpace().createDynamicNode();
    rootNode.attachChild(movingStaticNode);
    movingStaticNode.createBox("physics box");
    movingStaticNode.attachChild(
            new Box("box mesh", new Vector3f(0, 0, 0), 0.5f, 0.5f, 0.5f));
    updateMovingStaticNode();
    
    showPhysics = true;
    
    input = JoystickInput.get();
    LOG.info("Number of joysticks: " + input.getJoystickCount());
  }

  @Override
  protected void simpleUpdate() {
    rootNode.updateWorldData(timer.getTimeInSeconds());
    
    updateMovingStaticNode();
    
    saucerPhysicsNode.clearForce();
    ball.clearForce();
    
    saucerPhysicsNode.addForce(new Vector3f(
            input.getDefaultJoystick().getAxisValue(4),
            -input.getDefaultJoystick().getAxisValue(3), 0).mult(10));
    
    if (input.getDefaultJoystick().isButtonPressed(0)) {
      if (beam == null) {
        beam = new Node("beam");
        
        final Cylinder innerBeam = new Cylinder("inner beam", 2, 10, 0.05f, 1);
        
        final MaterialState innerBeamMaterialState = 
          display.getRenderer().createMaterialState();
        innerBeamMaterialState.setEnabled(true);
        innerBeamMaterialState.setDiffuse(new ColorRGBA(0, 0, 0, 0.25f));
        innerBeamMaterialState.setAmbient(new ColorRGBA(1, 1, 0, 0.25f));
        innerBeamMaterialState.setEmissive(new ColorRGBA(1, 1, 0, 0.25f));
        innerBeamMaterialState.setSpecular(new ColorRGBA(0, 0, 0, 0.25f));
        innerBeamMaterialState.setMaterialFace(MaterialState.MF_FRONT_AND_BACK);
        innerBeam.setRenderState(innerBeamMaterialState);
        beam.attachChild(innerBeam);
        
        final Cylinder outerBeam = new Cylinder("outer beam", 2, 10, 0.2f, 1);
        
        final MaterialState outerBeamMaterialState = 
          display.getRenderer().createMaterialState();
        outerBeamMaterialState.setEnabled(true);
        outerBeamMaterialState.setDiffuse(new ColorRGBA(0, 0, 0, 0.1f));
        outerBeamMaterialState.setAmbient(new ColorRGBA(0, 1, 0, 0.1f));
        outerBeamMaterialState.setEmissive(new ColorRGBA(0, 1, 0, 0.1f));
        outerBeamMaterialState.setSpecular(new ColorRGBA(0, 0, 0, 0.1f));
        outerBeamMaterialState.setMaterialFace(MaterialState.MF_FRONT_AND_BACK);
        outerBeam.setRenderState(outerBeamMaterialState);
        beam.attachChild(outerBeam);
        
        final AlphaState beamAlphaState =
          display.getRenderer().createAlphaState();
        beamAlphaState.setEnabled(true);
        beamAlphaState.setBlendEnabled(true);
        beamAlphaState.setSrcFunction(AlphaState.SB_SRC_ALPHA);
        beamAlphaState.setDstFunction(AlphaState.SB_ONE_MINUS_SRC_ALPHA);
        beamAlphaState.setTestFunction(AlphaState.TF_GREATER);
        beamAlphaState.setNeedsRefresh(true);
        beam.setRenderState(beamAlphaState);
        beam.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        
        rootNode.attachChild(beam);
        rootNode.updateRenderState();     
      }
      
      final float beamLength = ball.getWorldTranslation()
        .subtract(saucerPhysicsNode.getWorldTranslation()).length();
      beam.setLocalScale(new Vector3f(1, 1, beamLength));
      final Quaternion rotation = new Quaternion();
      rotation.lookAt(
          ball.getWorldTranslation().subtract(saucerPhysicsNode.getWorldTranslation()),
          Vector3f.UNIT_Y);
      beam.setLocalRotation(rotation);
      
      final Vector3f translation = new Vector3f();
      translation.interpolate(
              saucerPhysicsNode.getWorldTranslation(), ball.getWorldTranslation(), 0.5f);
      beam.setLocalTranslation(translation);

      final Vector3f force = 
        saucerPhysicsNode.getWorldTranslation().subtract(ball.getWorldTranslation())
        .normalize().mult(10);
      ball.addForce(force);
      saucerPhysicsNode.addForce(force.mult(-1));
      
    } else {
      if (beam != null) {
        beam.removeFromParent();
        beam = null;
      }
    }
    
    setCameraLocationAndZoom(saucerPhysicsNode, ball);
  }
  
  private void updateMovingStaticNode() {
    movingStaticNode.setLocalTranslation(
            5 * FastMath.sin(timer.getTimeInSeconds()), 5, 0);
    final Matrix3f rotation = new Matrix3f();
    rotation.loadIdentity();
    movingStaticNode.setLocalRotation(rotation);
    movingStaticNode.setLinearVelocity(
            new Vector3f(5 * FastMath.cos(timer.getTimeInSeconds()), 0, 0));
    movingStaticNode.setAngularVelocity(Vector3f.ZERO);
  }
  
  private void setCameraLocationAndZoom(Spatial... spatials) {
    float minX = Float.POSITIVE_INFINITY;
    float maxX = Float.NEGATIVE_INFINITY;
    float minY = Float.POSITIVE_INFINITY;
    float maxY = Float.NEGATIVE_INFINITY;
    for (Spatial spatial : spatials) {
      minX = Math.min(minX, spatial.getWorldTranslation().x);
      maxX = Math.max(maxX, spatial.getWorldTranslation().x);
      minY = Math.min(minY, spatial.getWorldTranslation().y);
      maxY = Math.max(maxY, spatial.getWorldTranslation().y);
    }
    
    minX -= 3;
    maxX += 3;
    minY -= 3;
    maxY += 3;
    
    final float boundingWidth = maxX - minX;
    final float boundingHeight = maxY - minY;
    final float centerX = minX + (boundingWidth / 2);
    final float centerY = minY + (boundingHeight / 2);
    
    final float screenWidth;
    final float screenHeight;
    if ((boundingWidth / boundingHeight) > (4f / 3f)) {
      // too wide
      screenWidth = boundingWidth;
      screenHeight = boundingWidth * (3f / 4f);
    } else {
      // too tall
      screenHeight = boundingHeight;
      screenWidth = boundingHeight * (4f / 3f);
    }
    
    cam.setLocation(new Vector3f(centerX, centerY, 1000));
    cam.setFrustum(
        990, 2000, 
        -screenWidth / 2, screenWidth / 2,
        screenHeight / 2, -screenHeight / 2);
  }

  public static void main( String[] args ) {
    JoystickInput.setProvider(InputSystem.INPUT_SYSTEM_LWJGL);
    Test2 test2 = new Test2();
    // test2.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
    test2.start();
  }
}
